<?php

namespace App\Models\Payment\Platform;

use App\Jobs\PaymentOrderPaid;
use App\Models\Common\Constant;
use App\Models\Payment\PaymentChannel;
use App\Models\Payment\PaymentOrder;
use App\Models\Payment\RefundOrder;
use App\Models\Payment\TradeType;
use Godruoyi\Snowflake\Snowflake;
use GuzzleHttp\Client;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;

class EbankPay extends BasePlatform implements BasePlatformInterface
{
    const GATEWAY = 'https://ebank.yantaibank.net'; // https://pay-st-yt.ebanktest.com.cn https://ebank.yantaibank.net

    const RULE = [
        'app_id' => 'required|string|min:4',
        'channl_id' => 'required|string|min:4',
        'mchnt_no' => 'required|string|min:4',
        'sub_mchnt_no' => 'string|min:4',
        'rsa_pub_cert_path' => 'required|string|min:4',
        'rsa_pri_cert_path' => 'required|string|min:4',
        'rsa_pri_cert_pwd' => 'required|string|min:4',
        'gateway' => 'string|min:10',
    ];

    const NOTIFY_CODE_SUCCESS = ['uop_code' => 'UC000000', 'uop_msg' => '支付结果通知成功！'];
    const NOTIFY_CODE_FAIL = ['uop_code' => '0', 'uop_msg' => ''];

    const CERTPWD = '111111'; // 111111 yqn@123

    protected $channlId; // 渠道标识
    protected $appId; // 应用ID
    protected $mchntNo; // 平台商户 PMxxxxx
    protected $subMchntNo; // 子商户号

    protected $rsaPrivateKey; // 加密私钥
    protected $snowflake;
    protected $uri = '';
    protected $apiCode = '';
    protected $version = 'v1';

    protected $uniqueNo = ''; // 请求唯一流水号
    protected $timestamp = ''; // 时间戳 YYYYMMDDHHmmssSSS精确到毫秒
    protected $aesKey = ''; // 加密密钥
    protected $encryptAesKey = ''; // 加密密钥密文
    protected $sign = ''; // 签名值
    protected $encryptBody = ''; // 加密后body

    protected $rsaPubCertPath = '';
    protected $rsaPriCertPath = '';
    protected $RSA_PRIVATE = '';
    protected $RSA_PUBLIC = '';
    protected $rsaPriCertPwd = '';// 私钥证书密码
    protected $orderCcy = '156'; // 币种，默认156人民币

    protected $paymentChannelId = '';

    public function __construct(PaymentChannel $paymentChannel, $refreshCache = false)
    {
        $this->paymentChannelId = $paymentChannel->id;
        $parameters = $paymentChannel->parameters ?: [];
        $this->appId = Arr::get($parameters, 'app_id');
        $this->channlId = Arr::get($parameters, 'channl_id');
        $this->mchntNo = Arr::get($parameters, 'mchnt_no');
        $this->subMchntNo = Arr::get($parameters, 'sub_mchnt_no');
        $this->rsaPubCertPath = Arr::get($parameters, 'rsa_pub_cert_path');
        $this->rsaPriCertPath = Arr::get($parameters, 'rsa_pri_cert_path');

        if ($rsaPriCertPwd = \Arr::get($parameters, 'rsa_pri_cert_pwd')) {
            $this->rsaPriCertPwd = trim($rsaPriCertPwd);
        } else {
            $this->rsaPriCertPwd = self::CERTPWD;
        }

        if ($gateway = \Arr::get($parameters, 'gateway')) {
            $this->gateway = trim($gateway, '/');
        } else {
            $this->gateway = self::GATEWAY;
        }

        self::rsaFileContent($refreshCache);

        $this->logger = Log::channel('payment-ebank');

        $this->snowflake = new Snowflake();
    }

    protected static function nonceStr($len = 16): string
    {
        return Str::random($len);
    }

    protected static function timestamp(): string
    {
        return date('YmdHis').'000';
    }

    protected function signature(): string
    {
        return '';
    }

    private function rsaFileContent(bool $refreshCache = false) {

        $rsaPubCacheKey = 'paymentChannel-rsaPub:' . $this->paymentChannelId;
        $rsaPriCacheKey = 'paymentChannel-rsaPri:' . $this->paymentChannelId;
        $expiredIn = 3600 * 8; // 缓存8小时

        if (!($this->RSA_PUBLIC = cache($rsaPubCacheKey)) || $refreshCache) {
            $rsaPubCertPath = storage_path($this->rsaPubCertPath);// 公钥证书路径
            // 公钥
            // 读取.CER文件的二进制内容
            $rsaPubCertData = file_get_contents($rsaPubCertPath);
            if (false === mb_stripos($rsaPubCertData, 'BEGIN CERTIFICATE')) {
                // 将二进制数据转换为PEM格式
                $rsaPubCertPemData = "-----BEGIN CERTIFICATE-----\n" .
                    chunk_split(base64_encode($rsaPubCertData), 64, "\n") .
                    "-----END CERTIFICATE-----\n";
            } else {
                $rsaPubCertPemData = $rsaPubCertData;
            }
            // 从证书内容创建X509对象
            $x509 = openssl_x509_read($rsaPubCertPemData);
            // 从X509对象中获取公钥资源
            $pubKeyResource = openssl_pkey_get_public($x509);
            // 如果需要，可以将公钥资源转换为字符串形式
            if ($pubKeyResource) {
                $pubKeyDetails = openssl_pkey_get_details($pubKeyResource);
                $this->RSA_PUBLIC = $pubKeyDetails['key'];
                cache([$rsaPriCacheKey => $this->RSA_PRIVATE], now()->addSeconds($expiredIn));
            } else {
                throw new \Exception('读取公钥失败');
            }
            // 清理资源
            openssl_pkey_free($pubKeyResource);
        }

        if (!($this->RSA_PRIVATE = cache($rsaPriCacheKey)) || $refreshCache) {
            $rsaPriCertPath = storage_path($this->rsaPriCertPath);// 私钥证书路径
            // 读取私钥证书内容
            $result = openssl_pkcs12_read(file_get_contents($rsaPriCertPath), $cert, $this->rsaPriCertPwd);
            if (!$result) {
                throw new \Exception('解密私钥证书失败');
            }
            //私钥
            $this->RSA_PRIVATE = $cert['pkey'];
            cache([$rsaPriCacheKey => $this->RSA_PRIVATE], now()->addSeconds($expiredIn));
        }
    }

    private function buildSignData()
    {
        // 业务参数json格式化
        $jsonBody = stripslashes(json_encode($this->body, JSON_UNESCAPED_UNICODE));
        // 对称加密&base64编码
        $sha256 = base64_encode(hex2bin(hash('sha256', $jsonBody)));
        //参与签名字段
        $signData = [
            'Body' => $sha256,
            'X-Uop-Api-Code' => $this->apiCode,
            'X-Uop-App-Id' => $this->appId,
            'X-Uop-Timestamp' => $this->timestamp,
            'X-Uop-Unique-No' => $this->uniqueNo
        ];
        // 排序
        ksort($signData);
        // 拼接签名字符串
        $signStr = '';
        foreach ($signData as $key => $value) {
            $signStr.= $key.'='.$value.'&';
        }
        $signContent = rtrim($signStr, '&');
        // 使用私钥对签名字符串加密 第一个参数是待加密的数据只能是string，第二个参数是加密后的数据,第三个参数是openssl_pkey_get_public返回的资源类型,第四个参数是填充方式
        openssl_sign($signContent, $sign, $this->RSA_PRIVATE,OPENSSL_ALGO_SHA256);
        //base64编码 得到签名
        $this->sign = base64_encode($sign);

        //公钥加密随机秘钥
        $public_key = openssl_pkey_get_public($this->RSA_PUBLIC);
        //第一个参数是待加密的数据只能是string，第二个参数是加密后的数据,第三个参数是openssl_pkey_get_public返回的资源类型,第四个参数是填充方式
        openssl_public_encrypt($this->aesKey, $crypted, $public_key, OPENSSL_PKCS1_PADDING);
        // 释放公钥资源
        openssl_pkey_free($public_key);
        $this->encryptAesKey = base64_encode($crypted);

        // 加密报文体
        $this->encryptBody = self::encrypt($jsonBody, $this->aesKey);
    }

    public function decryptResponse($responseArr): array {

        $result = ['code' => 0, 'uop_msg' => ''];

        if (!$rspbody = Arr::get($responseArr, 'body')) {
            $result['uop_msg'] = '返回报文为空';
            return $result;
        }
        if (!$X_Uop_Encrypted_Key = Arr::get($responseArr, 'headers.X-Uop-Encrypted-Key')) {
            if (!$X_Uop_Encrypted_Key = Arr::get($responseArr, 'headers.x-uop-encrypted-key')) {
                $result['uop_msg'] = '返回加密key为空';
                return $result;
            }
        }
        $X_Uop_Encrypted_Key = $X_Uop_Encrypted_Key[0];
        if (!$X_Uop_Unique_No = Arr::get($responseArr, 'headers.X-Uop-Unique-No')) {
            if (!$X_Uop_Unique_No = Arr::get($responseArr, 'headers.x-uop-unique-no')) {
                $result['uop_msg'] = '返回流水号为空';
                return $result;
            }
        }
        $X_Uop_Unique_No = $X_Uop_Unique_No[0];
        if (!$X_Uop_Signature = Arr::get($responseArr, 'headers.X-Uop-Signature')) {
            if (!$X_Uop_Signature = Arr::get($responseArr, 'headers.x-uop-signature')) {
                $result['uop_msg'] = '返回签名为空';
                return $result;
            }
        }
        $X_Uop_Signature = $X_Uop_Signature[0];
        if (!$X_Uop_Timestamp = Arr::get($responseArr, 'headers.X-Uop-Timestamp')) {
            $X_Uop_Timestamp = Arr::get($responseArr, 'headers.x-uop-timestamp');
        }
        $X_Uop_Timestamp && $X_Uop_Timestamp = $X_Uop_Timestamp[0];
        if (!$X_Uop_App_Id = Arr::get($responseArr, 'headers.X-Uop-App-Id')) {
            $X_Uop_App_Id = Arr::get($responseArr, 'headers.x-uop-app-id');
        }
        $X_Uop_App_Id && $X_Uop_App_Id = $X_Uop_App_Id[0];
        if (!$X_Uop_Api_Code = Arr::get($responseArr, 'headers.X-Uop-Api-Code')) {
            $X_Uop_Api_Code = Arr::get($responseArr, 'headers.x-uop-api-code');
        }
        $X_Uop_Api_Code && $X_Uop_Api_Code = $X_Uop_Api_Code[0];

        //私钥解密获取密钥
        $private_key = openssl_pkey_get_private($this->RSA_PRIVATE);
        if(!$private_key){
            $result['uop_msg'] = '私钥不可用';
            return $result;
        }
        $return_de = openssl_private_decrypt(base64_decode($X_Uop_Encrypted_Key), $decrypted, $private_key);
        if(!$return_de){
            $result['uop_msg'] = '解密失败,请检查私钥';
            return $result;
        }
        // 报文体解密
        $body = self::decrypt($rspbody, $decrypted);

//        Log::info('报文体解密', json_decode($body, true));

        $sha256Back = base64_encode(hex2bin(hash('sha256', $body)));
        //拼接字符串
        $signData = [
            'Body' => $sha256Back,
            'X-Uop-Unique-No' => $X_Uop_Unique_No
        ];
        $X_Uop_Api_Code && $signData['X-Uop-Api-Code'] = $X_Uop_Api_Code;
        $X_Uop_App_Id && $signData['X-Uop-App-Id'] = $X_Uop_App_Id;
        $X_Uop_Timestamp && $signData['X-Uop-Timestamp'] = $X_Uop_Timestamp;
        // 排序
        ksort($signData);
        // 拼接签名字符串
        $signStr = '';
        foreach ($signData as $key => $value) {
            $signStr.= $key.'='.$value.'&';
        }
        $signContentBack = rtrim($signStr, '&');

        $check = (bool)openssl_verify($signContentBack, base64_decode($X_Uop_Signature), $this->RSA_PUBLIC,OPENSSL_ALGO_SHA256);
        if (!$check) {
            $result['uop_msg'] = '验签失败';
            return $result;
        }

        $result['code'] = 1;
        $result['data'] = json_decode($body, true);
        return $result;
    }

    /**
     * 加密
     * @param $str
     * @param $key
     * @return string
     */
    function encrypt($str, $key) {
        $cipher = 'AES-128-CBC';
        $options = OPENSSL_RAW_DATA;
        $iv = str_repeat("\0", 16); // 十六进制0
        $encrypt_str = openssl_encrypt($str, $cipher, $key, $options, $iv);
        return base64_encode($encrypt_str);
    }

    /**
     * 解密
     * @param $str
     * @param $key
     * @return false|string
     */
    function decrypt($str, $key) {
        $cipher = 'AES-128-CBC';
        $options = OPENSSL_RAW_DATA;
        $iv = str_repeat("\0", 16); // 十六进制0
        return openssl_decrypt(base64_decode($str), $cipher, $key, $options, $iv); // $pad
    }

    protected function fire()
    {
        $this->uniqueNo = self::nonceStr(32);
        $this->timestamp = self::timestamp();
        $this->aesKey = openssl_random_pseudo_bytes(16); // 生成16位随机字节串

        self::buildSignData();

        $httpClient = new Client(['base_uri' => $this->gateway, 'timeout' => self::TIMEOUT, 'verify' => false]);
        $httpMethod = strtoupper($this->method);
        $respBody = [];

        $queryBody = ['headers' => [
            'Content-Type' => 'application/json;charset=utf-8',
            'X-Uop-Api-Code' => $this->apiCode,
            'X-Uop-App-Id' => $this->appId,
            'X-Uop-Unique-No' => $this->uniqueNo,
            'X-Uop-Timestamp' => $this->timestamp,
            'X-Uop-Encrypted-Key' => $this->encryptAesKey,
            'X-Uop-Signature' => $this->sign,
        ]];
        $queryBody['body'] = $this->encryptBody;

        try {
            $rawResponse = $httpClient->request($httpMethod, $this->uri.'/'.$this->version, $queryBody);
            $responseStr = optional(optional($rawResponse)->getBody())->getContents() ?: '';
            $responseArr['body'] = $responseStr;
            $responseArr['headers'] = $rawResponse->getHeaders();

            if (strpos($responseStr, 'uop_msg') !== false) {
                $respBody = @json_decode($responseStr, true) ?: [];
            }
            else {
                $decryptResponseArr = self::decryptResponse($responseArr);
                $responseArr['decrypt'] = $decryptResponseArr;
                if (1 === Arr::get($decryptResponseArr, 'code')) {
                    $respBody = Arr::get($decryptResponseArr, 'data');
                } else {
                    $respBody = $decryptResponseArr;
                }
            }
        } catch (\Throwable $exception) {
            $message = $exception->getMessage();
            if (strpos($message, 'cURL error 28:') !== false) {
                $message = '请求超时';
            }
            $responseArr = [
                'message' => $message
            ];
        } finally {

            $this->logger->info($this->name, ['gateway' => $this->gateway.$this->uri.'/'.$this->version, 'body' => $this->body, 'query' => $queryBody, 'response' => $responseArr]);

            if (isset($exception)) {
                throw $exception;
            }
        }

        $this->responseAdapter($respBody);

        return $respBody;
    }

    protected function notifySignCheck($rawNotifyDat): bool
    {
        $result = self::decryptResponse($rawNotifyDat);
        return 1 === Arr::get($result, 'code');
    }

    static public function optionalDatValidator(string $tradeType, array $optionalDat): array
    {
        return [];
    }

    public static function notifyDatFilter($rawNotifyDat): array
    {
        return [];
    }

    public function toPay(string $tradeType, PaymentOrder $paymentOrder)
    {
        if ($paymentOrder->paid_at || $paymentOrder->order_status === Constant::ORDER_STATUS_PAID) {
            throw new \Exception($paymentOrder->paid_code . '已完成支付的订单不可重复发起支付');
        }

        if (!$parkOrder = $paymentOrder->related) {
            throw new \Exception('未查询到临停订单');
        }

        $this->name = '申请支付参数:' . $tradeType;

        $amount = (string)($paymentOrder->amount/100);
        $toPayBody = [
            'header' => ['chanlId' => '816|'.$this->channlId],
            'orderExpiryTime' => '300',
            'mchntOrderId' => $paymentOrder->paid_code,
            'orderDetailList' => [[
                'orderCcy' => $this->orderCcy,
                'orderAmt' => $amount,
                'orderSettleAmt' => $amount,
                'mchntNo' => $this->subMchntNo,
                'mchntOrderDesc' => $paymentOrder->order_title,
                'mchntSubOrderId' => str_replace('-', '', $paymentOrder->related_id),
            ]],
            'frontBackUrl' => '',
            'notifyBackUrl' => $this->notifyUrl($paymentOrder->pay_type, $paymentOrder->payment_channel_id),
            'orderAmt' => $amount,
            'orderCcy' => $this->orderCcy,
            'extraParamList' => [],
            'userId' => '',
            'mchntNo' => $this->mchntNo,
            'orderDesc' => $paymentOrder->order_title,
            'openId' => '',
            'code' => ''
        ];

        switch ($tradeType) {
            case TradeType::WECHAT_JSAPI:
                $this->uri = '/uop/api/pay/paypro/wechatorder';
                $this->apiCode = 'OPC108950202000100000600';
                $toPayBody['extraParamList'] = [
                    [
                        'paramKey' => 'appId',
                        'paramValue' => $this->appId,
                        'sensitiveInfoFlag' => 'N'
                    ],
                    [
                        'paramKey' => 'accessType',
                        'paramValue' => 'ACCESS_WEB', // ACCESS_WEB微信公众号
                        'sensitiveInfoFlag' => 'N'
                    ],
                    [
                        'paramKey' => 'envData',
                        'paramValue' => '{\"browser\":\"WX\"}',
                        'sensitiveInfoFlag' => 'N'
                    ]
                ];
                break;
            case TradeType::WECHAT_MINI:
                $this->uri = '/uop/api/pay/paypro/applet/wechatorder';
                $this->apiCode = 'OPC108950202000181600600';
//                $toPayBody['extraParamList'] = [
//                    [
//                        'paramKey' => 'appId',
//                        'paramValue' => $this->appId,
//                        'sensitiveInfoFlag' => 'N'
//                    ],
//                    [
//                        'paramKey' => 'bizOrderId',
//                        'paramValue' => $paymentOrder->paid_code,
//                        'sensitiveInfoFlag' => 'N'
//                    ]
//                ];
                $toPayBody['extraParamList'] = [
                    [
                        'paramKey' => 'appId',
                        'paramValue' => $this->appId,
                        'sensitiveInfoFlag' => 'N'
                    ],
                    [
                        'paramKey' => 'accessType',
                        'paramValue' => 'ACCESS_WEB', // ACCESS_WEB 浏览器
                        'sensitiveInfoFlag' => 'N'
                    ],
                    [
                        'paramKey' => 'cashierType',
                        'paramValue' => 'H5',
                        'sensitiveInfoFlag' => 'N'
                    ],
                    [
                        'paramKey' => 'envData',
                        'paramValue' => 'WX',
                        'sensitiveInfoFlag' => 'N'
                    ]
                ];
                $toPayBody['openId'] = $paymentOrder->payer_id;
                if (!$paymentOrder->payer_id) {
                    throw new \Exception('openid为空');
                }
                break;
            case TradeType::WECHAT_NATIVE:
            case TradeType::ALI_NATIVE:
                $this->uri = '/uop/api/pay/paypro/payment';
                $this->apiCode = 'OPC108950202000100000100';

                $extraParamList = [
                    [
                        'paramKey' => 'appId',
                        'paramValue' => $this->appId,
                        'sensitiveInfoFlag' => 'N'
                    ],
                    [
                        'paramKey' => 'accessType',
                        'paramValue' => 'ACCESS_WEB', // ACCESS_WEB 浏览器
                        'sensitiveInfoFlag' => 'N'
                    ],
                    [
                        'paramKey' => 'cashierType',
                        'paramValue' => 'H5',
                        'sensitiveInfoFlag' => 'N'
                    ]
                ];
                if (TradeType::WECHAT_NATIVE === $tradeType) {
                    if (!$paymentOrder->payer_id) {
                        throw new \Exception('openid为空');
                    }
                    $extraParamList[] = [
                        'paramKey' => 'envData',
                        'paramValue' => '{\"browser\":\"WX\"}',
                        'sensitiveInfoFlag' => 'N'
                    ];
                    $extraParamList[] = [
                        'paramKey' => 'openId',
                        'paramValue' => $paymentOrder->payer_id,
                        'sensitiveInfoFlag' => 'N'
                    ];
                }
                if (TradeType::ALI_NATIVE === $tradeType) {
                    $extraParamList[] = [
                        'paramKey' => 'envData',
                        'paramValue' => '{\"browser\":\"AP\"}',
                        'sensitiveInfoFlag' => 'N'
                    ];
                }
                $toPayBody = [
                    'header' => ['chanlId' => '816|'.$this->channlId],
                    'userId' => '',
                    'mchntNo' => $this->mchntNo,
                    'mchntOrderId' => $paymentOrder->paid_code,
//                    'bizSceneType' => '', // 业务场景类型
                    'orderCcy' => $this->orderCcy,
                    'orderAmt' => $amount,
                    'orderExpiryTime' => '300',
                    'orderDesc' => $paymentOrder->order_title,
                    'frontBackUrl' => config('app.h5_url').'/pages/success/index?order_code=' . $parkOrder->order_code, // 回跳地址
                    'notifyBackUrl' => $this->notifyUrl($paymentOrder->pay_type, $paymentOrder->payment_channel_id),
                    'extraParamLists' => $extraParamList,
                    'orderDetailLists' => [[
                        'orderCcy' => $this->orderCcy,
                        'orderAmt' => $amount,
                        'orderSettleAmt' => $amount,
                        'mchntNo' => $this->subMchntNo,
                        'mchntOrderDesc' => $paymentOrder->order_title,
                        'mchntSubOrderId' => str_replace('-', '', $paymentOrder->related_id),
                    ]],
                ];
                break;
            default:
                throw new \Exception('不支持的支付方式');
        }

        $this->body = $toPayBody;

        $payResponse = $this->fire();

        $error = '';
        if (Arr::get($payResponse, 'return_code') === 'SUCCESS' && Arr::get($payResponse, 'result_code') === 'SUCCESS') {
        } else {
            $error = Arr::get($payResponse, 'return_msg') ?: '未知原因';
        }
        $paymentOrder->error = $error;
        $paymentOrder->save();

        return $payResponse;
    }

    public function toQuery(PaymentOrder $paymentOrder)
    {
        $this->name = '订单查询';
        $this->uri = '/uop/api/pay/paypro/paymentstats';
        $this->apiCode = 'OPC108950202000100000200';

        $toPayBody = [
            'header' => ['chanlId' => '816|'.$this->channlId],
            'mchntNo' => $this->mchntNo,
            'mchntOrderId' => $paymentOrder->paid_code,
            'orderChanlId' => $this->channlId
        ];

        $this->body = $toPayBody;

        return $this->fire();
    }

    public function toRefund(RefundOrder $refundOrder)
    {
        $this->name = '订单退款';
        $this->uri = '/uop/api/pay/paypro/payrefund';
        $this->apiCode = 'OPC108950202000100000400';

        $paymentOrder = PaymentOrder::where('paid_code', $refundOrder->paid_code)->first();

        $amount = (string)($refundOrder->refund_amount/100);
        $toPayBody = [
            'header' => ['chanlId' => '816|'.$this->channlId],
            'mchntNo' => $this->mchntNo,
            'mchntOrderId' => $refundOrder->paid_code,
            'mchntRefundId' => $refundOrder->refund_code,
            'orderAmt' => $amount,
        ];

        // 部分退款传
        if ($paymentOrder && $refundOrder->refund_amount < $paymentOrder->amount) {
            $toPayBody['refundOrderDetailLists'] = [[
                'orderCcy' => $this->orderCcy,
                'orderAmt' => $amount,
                'orderSettleAmt' => $amount,
                'mchntNo' => $this->subMchntNo,
                'mchntSubOrderId' => str_replace('-', '', $paymentOrder->related_id),
            ]];
        }

        $this->body = $toPayBody;

        return $this->fire();
    }

    public function toRefundT($amount, $paidCode)
    {
        $this->name = '订单退款';
        $this->uri = '/uop/api/pay/paypro/payrefund';
        $this->apiCode = 'OPC108950202000100000400';

        $snowflake = new Snowflake();
        $toPayBody = [
            'header' => ['chanlId' => '816|'.$this->channlId],
            'mchntNo' => $this->mchntNo,
            'mchntOrderId' => $paidCode,
            'mchntRefundId' => $snowflake->id(),
            'orderAmt' => (string)$amount,
        ];

        $this->body = $toPayBody;

        return $this->fire();
    }

    public function toClose(PaymentOrder $paymentOrder)
    {
        $this->name = '订单关闭';
        $this->uri = '/uop/api/pay/paypro/orderclosed';
        $this->apiCode = 'OPC108950202000100000500';

        $toPayBody = [
            'header' => ['chanlId' => '816|'.$this->channlId],
            'mchntNo' => $this->mchntNo,
            'mchntOrderId' => $paymentOrder->paid_code,
        ];

        $this->body = $toPayBody;

        return $this->fire();
    }

    public function handleNotify(PaymentOrder $paymentOrder, array $notifyDat)
    {
        $paymentSuccessful = false;
        $logName = $paymentOrder->pay_type . ':' . $paymentOrder->payment_channel_id;

        if (!$outTransactionID = Arr::get($notifyDat, 'paySeqNum')) {
            Log::channel('payment-notify-error')->info($logName, ['err' => '支付回调缺失重要参数流水单号', 'notifyDat' => $notifyDat, 'paid_code' => $paymentOrder->paid_code]);
            return self::NOTIFY_CODE_FAIL;
        }

        if ($paymentOrder->order_status === Constant::ORDER_STATUS_PAID) {
            if ($paymentOrder->out_trade_no && ($paymentOrder->out_trade_no !== $outTransactionID)) {
                Log::channel('payment-notify-error')->info($logName, ['err' => '订单重复支付(out_transaction_id)', 'notifyDat' => $notifyDat, 'paid_code' => $paymentOrder->paid_code]);
                return self::NOTIFY_CODE_SUCCESS;
            } else {
                Log::channel('payment-notify-error')->info($logName, ['err' => '订单已支付', 'notifyDat' => $notifyDat, 'paid_code' => $paymentOrder->paid_code]);
            }
            return self::NOTIFY_CODE_SUCCESS;
        }

        $paidAmount = intval(floatval(Arr::get($notifyDat, 'orderAmt'))*100);

        if ($paidAmount !== $paymentOrder->amount) {
            Log::channel('payment-notify-error')->info($logName, ['err' => "回调通知的支付金额与订单的支付金额不一致({$paidAmount}/{$paymentOrder->amount})", 'notifyDat' => $notifyDat, 'paid_code' => $paymentOrder->paid_code]);
            return self::NOTIFY_CODE_FAIL;
        }

        try {

            if (Arr::get($notifyDat, 'payStatus') === '1') {

                $paymentOrder->paid_at = now()->toDateTimeString();
                $paymentOrder->out_trade_no = $outTransactionID;
                $paymentOrder->frozen_balance_amount = 0;
                $paymentOrder->frozen_point = 0;
                $paymentOrder->order_status = Constant::ORDER_STATUS_PAID;

                $paymentOrder->save();
                $paymentSuccessful = true;
            }
        } catch (\Throwable $exception) {
            Log::channel('payment-notify-error')->info($logName, ['err' => $exception->getMessage(), 'notifyDat' => $notifyDat, 'paid_code' => $paymentOrder->paid_code]);
        } finally {
            if ($paymentSuccessful) {
                //处理后续的各类系统订单逻辑
                PaymentOrderPaid::dispatch($paymentOrder)->onQueue(Constant::QUEUE_PAYMENT);

                return self::NOTIFY_CODE_SUCCESS;
            }
        }
        return self::NOTIFY_CODE_FAIL;
    }

    public function responseAdapter(array &$response = []): array
    {
        $resultCode = Arr::get($response, 'uop_code');

        if ($resultCode === 'UC000000') {
            $response['result_code'] = $response['return_code'] = 'SUCCESS';
            $response['return_msg'] = 'OK';

            $response['trade_state_desc'] = $response['trade_state'] = strval(Arr::get($response, 'result_code'));

            // 收银台 onlineCasherUrl
            // 微信公众号data.authUrl

            if ($qrCode = Arr::get($response, 'uop_data.onlineCasherUrl')) {
                $response['pay_info'] = [
                    'qr_code' => $qrCode
                ];
            } else {
                if ($pay_info = Arr::get($response, 'uop_data.payInfo')) {
                    $response['pay_info'] = $pay_info; // json_decode($pay_info, true);
                }
            }
        } else {
            $errCode = $resultCode;
            $errMsg = Arr::get($response, 'uop_msg') ?: 'ERROR';
            $response['result_code'] = $response['return_code'] = 'FAIL';
            $response['return_msg'] = $response['trade_state_desc'] = $errMsg;
            $response['trade_state'] = $errCode;
        }

        return $response;
    }

    public function paidCode(): string
    {
        return $this->snowflake->id();
    }
}
